/*


Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package controllers

import (
	"context"
	"fmt"
	"github.com/go-logr/logr"
	"k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/apimachinery/pkg/runtime"
	ctrl "sigs.k8s.io/controller-runtime"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/reconcile"

	customv1 "K3d/api/v1"
)

type OwnResource interface {
	// 根据Unit的指定，生成相应的各类own build-in资源对象，用作创建或更新
	MakeOwnResource(instance *customv1.K3d, logger logr.Logger, scheme *runtime.Scheme) (interface{}, error)

	// 判断此资源是否已存在
	OwnResourceExist(instance *customv1.K3d, client client.Client, logger logr.Logger) (bool, interface{}, error)

	// 获取Unit对应的own build-in资源的状态，用来填充Unit的status字段
	UpdateOwnResourceStatus(instance *customv1.K3d, client client.Client, logger logr.Logger) (*customv1.K3d, error)

	// 创建/更新 Unit对应的own build-in资源
	ApplyOwnResource(instance *customv1.K3d, client client.Client, logger logr.Logger, scheme *runtime.Scheme) error
}

// K3dReconciler reconciles a K3d object
type K3dReconciler struct {
	client.Client
	Log    logr.Logger
	Scheme *runtime.Scheme
}

// +kubebuilder:rbac:groups=custom.my.domain,resources=k3ds,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=batch,resources=jobs,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=custom.my.domain,resources=k3ds/status,verbs=get;update;patch

func (r *K3dReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
	ctx := context.Background()
	_ = r.Log.WithValues("K3d", req.NamespacedName)

	// your logic here
	// panic recovery
	defer func() {
		if rec := recover(); r != nil {
			switch x := rec.(type) {
			case error:
				r.Log.Error(x, "Reconcile error")
			}
		}
	}()

	instance := &customv1.K3d{}

	// 1. Get操作,获取 K3d object
	err := r.Get(ctx, req.NamespacedName, instance)
	if err != nil {
		if errors.IsNotFound(err) {
			// Object not found, return.  Created objects are automatically garbage collected.
			// For additional cleanup logic use finalizers.
			return reconcile.Result{}, nil
		}
	}

	// 3. 创建或更新操作
	// 3.1 根据Unit.spec 生成Unit关联的所有own build-in resource
	ownResources, err := r.getOwnResources(instance)
	if err != nil {
		msg := fmt.Sprintf("%s %s Reconciler.getOwnResource() function error", instance.Namespace, instance.Name)
		r.Log.Error(err, msg)
		return ctrl.Result{}, err
	}

	// 3.2 判断各own resource 是否存在，不存在则创建，存在则判断spec是否有变化，有变化则更新
	for _, ownResource := range ownResources {
		if err = ownResource.ApplyOwnResource(instance, r.Client, r.Log, r.Scheme); err != nil {
			r.Log.Error(err, "创建或更新失败")
		}
	}

	return ctrl.Result{}, nil
}

// Demo.Spec生成其所有的own resource
func (r *K3dReconciler) getOwnResources(instance *customv1.K3d) ([]OwnResource, error) {
	var ownResources []OwnResource

	// Deployment加入到ownResources中
	// 根据type判断当前的操作是create还是delete集群
	operateType := instance.Spec.Type
	if operateType == "create" {
		if instance.Spec.Deployment != nil {
			createDeployment, _ := r.getCreateDeployment(instance)
			ownResources = append(ownResources, &createDeployment)
		}
		if instance.Spec.Job != nil {
			createJob, _ := r.getCreateJob(instance)
			ownResources = append(ownResources, &createJob)
		}
	} else if operateType == "delete" {
		if instance.Spec.Deployment != nil {
			deleteDeployment, _ := r.getDeleteDeployment(instance)
			ownResources = append(ownResources, &deleteDeployment)
		}
		if instance.Spec.Job != nil {
			deleteJob, _ := r.getDeleteJob(instance)
			ownResources = append(ownResources, &deleteJob)
		}
	} else {
		r.Log.Error(nil, "当前type类型校验失败，当前type为："+operateType)
	}

	return ownResources, nil
}

func (r *K3dReconciler) SetupWithManager(mgr ctrl.Manager) error {
	return ctrl.NewControllerManagedBy(mgr).
		For(&customv1.K3d{}).
		Complete(r)
}
